#include "stdafx.h"
#include "log/logger.hpp"
#include "log/formatter.hpp"
#include "log/logregistry.hpp"
#include "utils/numberformatter.hpp"
using JHCPP::utils::CNumberFormatter;
#include "charset/string.hpp"
using namespace JHCPP::charset;


namespace JHCPP
{
	namespace log
	{	
		CLogger::LoggerMap* CLogger::m_pLoggerMap = 0;
		CMutex CLogger::m_mapMtx;
		const std::string CLogger::ROOT;


		CLogger::CLogger(const std::string& name, CChannel* pChannel, int level)
		: m_name(name), m_pChannel(pChannel), m_level(level)
		{
			if (pChannel) pChannel->add();
		}

		CLogger::~CLogger()
		{
			if (m_pChannel) m_pChannel->release();
		}
	
		void CLogger::setChannel(CChannel* pChannel)
		{
			if (m_pChannel) m_pChannel->release();
			m_pChannel = pChannel;
			if (m_pChannel) m_pChannel->add();
		}

		CChannel* CLogger::getChannel() const
		{
			return m_pChannel;
		}

		void CLogger::setLevel(int level)
		{
			m_level = level;
		}

		void CLogger::setLevel(const std::string& level)
		{
			setLevel(parseLevel(level));
		}

		void CLogger::setProperty(const std::string& name, const std::string& value)
		{
			if (name == "channel")
				setChannel(CLogRegistry::defaultRegistry().channelForName(value));
			else if (name == "level")
				setLevel(value);
			else 
				CChannel::setProperty(name, value);
		}

		void CLogger::log(const CMessage& msg)
		{
			if (m_level >= msg.getPriority() && m_pChannel)
			{
				m_pChannel->log(msg);
			}
		}

		void CLogger::log(const CException& exc)
		{
			error(exc.displayText());
		}

		void CLogger::log(const CException& exc, const char* file, int line)
		{
			error(exc.displayText(), file, line);
		}

		void CLogger::dump(const std::string& msg, const void* buffer, std::size_t length, CMessage::Priority prio)
		{
			if (m_level >= prio && m_pChannel)
			{
				std::string text(msg);
				formatDump(text, buffer, length);
				m_pChannel->log(CMessage(m_name, text, prio));
			}
		}

		void CLogger::setLevel(const std::string& name, int level)
		{
			CMutex::ScopedLock lock(m_mapMtx);

			if (m_pLoggerMap)
			{
				std::string::size_type len = name.length();
				for (LoggerMap::iterator it = m_pLoggerMap->begin(); it != m_pLoggerMap->end(); ++it)
				{
					if (len == 0 || 
						(it->first.compare(0, len, name) == 0 && (it->first.length() == len || it->first[len] == '.')))
					{
						it->second->setLevel(level);
					}
				}
			}
		}

		void CLogger::setChannel(const std::string& name, CChannel* pChannel)
		{
			CMutex::ScopedLock lock(m_mapMtx);

			if (m_pLoggerMap)
			{
				std::string::size_type len = name.length();
				for (LoggerMap::iterator it = m_pLoggerMap->begin(); it != m_pLoggerMap->end(); ++it)
				{
					if (len == 0 ||
						(it->first.compare(0, len, name) == 0 && (it->first.length() == len || it->first[len] == '.')))
					{
						it->second->setChannel(pChannel);
					}
				}
			}
		}

		void CLogger::setProperty(const std::string& loggerName, const std::string& propertyName, const std::string& value)
		{
			CMutex::ScopedLock lock(m_mapMtx);

			if (m_pLoggerMap)
			{
				std::string::size_type len = loggerName.length();
				for (LoggerMap::iterator it = m_pLoggerMap->begin(); it != m_pLoggerMap->end(); ++it)
				{
					if (len == 0 ||
						(it->first.compare(0, len, loggerName) == 0 && (it->first.length() == len || it->first[len] == '.')))
					{
						it->second->setProperty(propertyName, value);
					}
				}
			}
		}

		std::string CLogger::format(const std::string& fmt, const std::string& arg)
		{
			std::string args[] =
			{
				arg
			};
			return format(fmt, 1, args);
		}

		std::string CLogger::format(const std::string& fmt, const std::string& arg0, const std::string& arg1)
		{
			std::string args[] =
			{
				arg0,
				arg1
			};
			return format(fmt, 2, args);
		}

		std::string CLogger::format(const std::string& fmt, const std::string& arg0, const std::string& arg1, const std::string& arg2)
		{
			std::string args[] =
			{
				arg0,
				arg1,
				arg2
			};
			return format(fmt, 3, args);
		}

		std::string CLogger::format(const std::string& fmt, const std::string& arg0, const std::string& arg1, const std::string& arg2, const std::string& arg3)
		{
			std::string args[] =
			{
				arg0,
				arg1,
				arg2,
				arg3
			};
			return format(fmt, 4, args);
		}

		std::string CLogger::format(const std::string& fmt, int argc, std::string argv[])
		{
			std::string result;
			std::string::const_iterator it = fmt.begin();
			while (it != fmt.end())
			{
				if (*it == '$')
				{
					++it;
					if (*it == '$')
					{
						result += '$';
					}
					else if (*it >= '0' && *it <= '9')
					{
						int i = *it - '0';
						if (i < argc)
							result += argv[i];
					}
					else
					{
						result += '$';
						result += *it;
					}
				}
				else result += *it;
				++it;
			}
			return result;
		}

		void CLogger::formatDump(std::string& message, const void* buffer, std::size_t length)
		{
			const int BYTES_PER_LINE = 16;

			message.reserve(message.size() + length*6);
			if (!message.empty()) message.append("\n");
			unsigned char* base = (unsigned char*) buffer;
			std::size_t addr = 0;
			while (addr < length)
			{
				if (addr > 0) message.append("\n");
				message.append(CNumberFormatter::formatHex(addr, 4));
				message.append("  ");
				int offset = 0;
				while (addr + offset < length && offset < BYTES_PER_LINE)
				{
					message.append(CNumberFormatter::formatHex(base[addr + offset], 2));
					message.append(offset == 7 ? "  " : " ");
					++offset;
				}
				if (offset < 7) message.append(" ");
				while (offset < BYTES_PER_LINE) { message.append("   "); ++offset; }
				message.append(" ");
				offset = 0;
				while (addr + offset < length && offset < BYTES_PER_LINE)
				{
					unsigned char c = base[addr + offset];
					message += (c >= 32 && c < 127) ? (char) c : '.';
					++offset;
				}
				addr += BYTES_PER_LINE;
			}
		}

		CLogger& CLogger::get(const std::string& name)
		{
			CMutex::ScopedLock lock(m_mapMtx);

			return unsafeGet(name);
		}

		CLogger& CLogger::unsafeGet(const std::string& name)
		{
			CLogger* pLogger = find(name);
			if (!pLogger)
			{
				if (name == ROOT)
				{
					pLogger = new CLogger(name, 0, CMessage::PRIO_INFORMATION);
				}
				else
				{
					CLogger& par = parent(name);
					pLogger = new CLogger(name, par.getChannel(), par.getLevel());
				}
				add(pLogger);
			}
			return *pLogger;
		}

		CLogger& CLogger::create(const std::string& name, CChannel* pChannel, int level)
		{
			CMutex::ScopedLock lock(m_mapMtx);

			if (find(name)) throw ExistsException();
			CLogger* pLogger = new CLogger(name, pChannel, level);
			add(pLogger);
			return *pLogger;
		}

		CLogger& CLogger::root()
		{
			CMutex::ScopedLock lock(m_mapMtx);

			return unsafeGet(ROOT);
		}

		CLogger* CLogger::has(const std::string& name)
		{
			CMutex::ScopedLock lock(m_mapMtx);

			return find(name);
		}

		void CLogger::shutdown()
		{
			CMutex::ScopedLock lock(m_mapMtx);

			if (m_pLoggerMap)
			{
				for (LoggerMap::iterator it = m_pLoggerMap->begin(); it != m_pLoggerMap->end(); ++it)
				{
					it->second->release();
				}
				delete m_pLoggerMap;
				m_pLoggerMap = 0;
			}
		}

		CLogger* CLogger::find(const std::string& name)
		{
			if (m_pLoggerMap)
			{
				LoggerMap::iterator it = m_pLoggerMap->find(name);
				if (it != m_pLoggerMap->end())
					return it->second;
			}
			return 0;
		}

		void CLogger::destroy(const std::string& name)
		{
			CMutex::ScopedLock lock(m_mapMtx);

			if (m_pLoggerMap)
			{
				LoggerMap::iterator it = m_pLoggerMap->find(name);
				if (it != m_pLoggerMap->end())
				{
					it->second->release();
					m_pLoggerMap->erase(it);
				}
			}
		}

		void CLogger::names(std::vector<std::string>& names)
		{
			CMutex::ScopedLock lock(m_mapMtx);

			names.clear();
			if (m_pLoggerMap)
			{
				for (LoggerMap::const_iterator it = m_pLoggerMap->begin(); it != m_pLoggerMap->end(); ++it)
				{
					names.push_back(it->first);
				}
			}
		}

		CLogger& CLogger::parent(const std::string& name)
		{
			std::string::size_type pos = name.rfind('.');
			if (pos != std::string::npos)
			{
				std::string pname = name.substr(0, pos);
				CLogger* pParent = find(pname);
				if (pParent)
					return *pParent;
				else
					return parent(pname);
			}
			else return unsafeGet(ROOT);
		}

		int CLogger::parseLevel(const std::string& level)
		{
			if (icompare(level, "none") == 0)
				return 0;
			else if (icompare(level, "fatal") == 0)
				return CMessage::PRIO_FATAL;
			else if (icompare(level, "critical") == 0)
				return CMessage::PRIO_CRITICAL;
			else if (icompare(level, "error") == 0)
				return CMessage::PRIO_ERROR;
			else if (icompare(level, "warning") == 0)
				return CMessage::PRIO_WARNING;
			else if (icompare(level, "notice") == 0)
				return CMessage::PRIO_NOTICE;
			else if (icompare(level, "information") == 0)
				return CMessage::PRIO_INFORMATION;
			else if (icompare(level, "debug") == 0)
				return CMessage::PRIO_DEBUG;
			else if (icompare(level, "trace") == 0)
				return CMessage::PRIO_TRACE;
			else
				throw InvalidArgumentException("Not a valid log level", level);
		}

		void CLogger::add(CLogger* pLogger)
		{
			if (!m_pLoggerMap)
				m_pLoggerMap = new LoggerMap;
			m_pLoggerMap->insert(LoggerMap::value_type(pLogger->name(), pLogger));
		}

	}//end of namespace log
}//end of namespace JHCPP
